﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace MetalSoda
{
    /// <summary>
    /// SSB contract
    /// </summary>
    [DebuggerDisplay("Contract: {Name}, MessageTypes: {MessageTypesCount}, Services: {ServicesCount}, Queues: {QueuesCount}")]
    [Serializable]
    public class Contract : IDdlObject
    {
        /// <summary>
        /// Contract name
        /// </summary>
        public readonly string Name;

        #region Private Fields
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly OneToManyAssociation<Contract, MessageType> m_messageTypeAssociations;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly OneToManyAssociation<Contract, Service> m_serviceAssociations;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly Dictionary<MessageType, SentBy> m_messageTypeUsage = new Dictionary<MessageType, SentBy>(); 
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MetalSoda.Contract"/> class.
        /// </summary>
        /// <param name="name">Contract name</param>
        public Contract(string name)
        {
            Name = name;
            m_messageTypeAssociations = new OneToManyAssociation<Contract, MessageType>(this,
                added: args => args.Ref.AddContract(this, SentBy(args.Ref)),
                removed: args => args.Ref.RemoveContract(this));
            m_serviceAssociations = new OneToManyAssociation<Contract, Service>(this,
                added: args => args.Ref.AddContract(this),
                removed: args => args.Ref.RemoveContract(this));
        }

        #region Properties
        /// <summary>
        /// Gets messages that can be sent on this contract.
        /// </summary>
        public IEnumerable<MessageType> MessageTypes { get { return m_messageTypeAssociations.AsEnumerable(); } }

        /// <summary>
        /// Gets services supporting this contract.
        /// </summary>
        public IEnumerable<Service> Services { get { return m_serviceAssociations.AsEnumerable(); } }

        /// <summary>
        /// Gets queues receiving messages sent onthis contract
        /// </summary>
        public IEnumerable<Queue> Queues { get { return m_serviceAssociations.Select(s => s.Queue).Where(q => q != null).Distinct(); } }

        /// <summary>
        /// Gets total number of messages that can be sent on this contract
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int MessageTypesCount { get { return m_messageTypeAssociations.Count; } }

        /// <summary>
        /// Gets total number of queues that can receive messages sent on this contract
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int QueuesCount { get { return Queues.Count(); } }

        /// <summary>
        /// Gets total number of services that implement this contract
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int ServicesCount { get { return m_serviceAssociations.Count; } } 
        #endregion

        #region Methods
        /// <summary>
        /// Adds the given service to the services implementing this contract.
        /// </summary>
        /// <param name="service">Service that will be included in services implementing this contract.</param>
        /// <returns>Self</returns>
        public Contract AddService(Service service)
        {
            if (Object.ReferenceEquals(service, null)) { throw new ArgumentNullException("service"); }
            if (Service(service.Name) != null)
            {
                if (Service(service.Name) == service) { return this; }
                var msg = string.Format("The {0} service already exists.", service.Name);
                throw new InvalidOperationException(msg);
            }
            m_serviceAssociations.Add(service);
            return this;
        }

        /// <summary>
        /// Unregisters the given service so that it will not implement this contract.
        /// </summary>
        /// <param name="service">Service to be excluded from services implementing this contract.</param>
        /// <returns></returns>
        public Contract RemoveService(Service service)
        {
            var member = Service(service.Name);
            if (Object.ReferenceEquals(member, null) == false) { m_serviceAssociations.Remove(member); }
            return this;
        }

        /// <summary>
        /// Adds the given message type to the message types supported by this contract.
        /// </summary>
        /// <param name="messageType">Message type to be enabled on this contract.</param>
        /// <param name="sendBy">Type of the sending endpoint</param>
        /// <returns>Self</returns>
        public Contract AddMessageType(MessageType messageType, SentBy sendBy)
        {
            if (Object.ReferenceEquals(messageType, null)) { throw new ArgumentNullException("messageType"); }
            if (m_messageTypeUsage.ContainsKey(messageType))
            {
                if (m_messageTypeUsage[messageType] == sendBy) { return this; }
                var msg = string.Format("The {0} message type already exists.", messageType.Name);
                throw new InvalidOperationException(msg);
            }
            m_messageTypeUsage.Add(messageType, sendBy);
            m_messageTypeAssociations.Add(messageType);
            return this;
        }

        /// <summary>
        /// Unregisters the given message type so that it will not be supported by this contract.
        /// </summary>
        /// <param name="messageType">Message type to be excluded from the types supported by this contract.</param>
        /// <returns>Self</returns>
        public Contract RemoveMessageType(MessageType messageType)
        {
            var member = MessageType(messageType.Name);
            if (Object.ReferenceEquals(member, null) == false)
            {
                m_messageTypeUsage.Remove(messageType);
                m_messageTypeAssociations.Remove(messageType);
            }
            return this;
        }

        /// <summary>
        /// Finds by name the service implementing this contract.
        /// </summary>
        /// <param name="serviceName">Service name</param>
        /// <returns>Instance of the service found.</returns>
        public Service Service(string serviceName)
        {
            return m_serviceAssociations.Where(s => Utils.AreSame(s.Name, serviceName)).SingleOrDefault();
        }

        /// <summary>
        /// Gets the type of the service endpoint for the given message type.
        /// </summary>
        /// <param name="messageType"></param>
        /// <returns></returns>
        public SentBy SentBy(MessageType messageType)
        {
            SentBy sendBy = MetalSoda.SentBy.Any;
            m_messageTypeUsage.TryGetValue(messageType, out sendBy);
            return sendBy;
        }

        /// <summary>
        /// Finds the type of the service endpoint for the given message type name.
        /// </summary>
        /// <param name="messageTypeName">Name of the message type.</param>
        /// <returns>Type of the service endpoint</returns>
        public SentBy SendBy(string messageTypeName)
        {
            return SentBy(MessageType(messageTypeName));
        }

        /// <summary>
        /// Finds by name the message type supported by this contract.
        /// </summary>
        /// <param name="messageTypeName">Name of the message type</param>
        /// <returns>Message type instance</returns>
        public MessageType MessageType(string messageTypeName)
        {
            return m_messageTypeAssociations.Where(mt => Utils.AreSame(mt.Name, messageTypeName)).SingleOrDefault();
        } 
        #endregion

        #region IDdlObject Implementation
        /// <summary>
        /// Generates a sequence on the CREATE batches for this contract.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateCreateBatches()
        {
            yield return new SQLScript.CreateContractRuntimeTextTemplate(this).TransformText();
        }

        /// <summary>
        /// Generates a sequence on the ALTER batches for this contract.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateAlterBatches() { yield break; }


        /// <summary>
        /// Generates a sequence on the DELETE batches for this contract.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateDeleteBatches()
        {
            yield return new SQLScript.DeleteContractRuntimeTextTemplate(this).TransformText();
        } 
        #endregion
    }
}
